﻿using System;
using System.Collections.Generic;
using System.Linq;

using btmerc.API.Enums;
using btmerc.API.Schemas.Interfaces;

namespace btmerc.API.Schemas
{
    public partial class Force : IForce
    {
        #region Public Properties
        public IEnumerable<ForceCharacter> Personnel
        {
            get
            {
                return Troops.SelectMany(t => t.Character);
            }
        }

        public IEnumerable<ForceCharacter> CombatPersonnel 
        {
            get
            {
                return Troops.Where(t => t.IsCombatTroop).SelectMany(t => t.Character);
            }
        }

        public Character Commander { get; private set; }
        #endregion

        public Force()
        {
            this.historyField = new History();
            this.equipmentField = new List<Component>();
            this.jumpshipsField = new List<Jumpship>();
            this.auxDropshipsField = new List<Dropship>();
            this.ownedDropshipsField = new List<Dropship>();
            this.troopsField = new List<Troop>();
            this.unitField = new Unit();
        }

        public IEnumerable<Vehicle> GetVehicles()
        {
            return Troops.Where(t => t.IsCombatTroop).Select(t => t.Item).Where(v => v != null);
        }

        public IEnumerable<Vehicle> GetVehicles(TroopType type)
        {
            return Troops.Where(t => t.Type == type).Select(t => t.Item).Where(v => v != null);
        }

        public IEnumerable<Troop> GetTroops(TroopType type)
        {
            return Troops.Where(t => t.Type == type);
        }

        public Quality GetAverageCombatQuality()
        {
            return CalculateAverageQuality(CombatPersonnel);
        }

        public TroopType GetMainForceType()
        {
            throw new NotImplementedException();
        }

        public Quality GetMainForceCombatQuality()
        {
            throw new NotImplementedException();
        }

        public IEnumerable<ExecutedContract> GetContractHistory()
        {
            return History.ContractHistory;
        }

        public Dropships GetOwnedDropships()
        {
            return new Dropships(OwnedDropships);
        }

        public Dropships GetAuxillaryDropships()
        {
            return new Dropships(AuxDropships);
        }

        public TroopCount GetTroopTransportNeeded()
        {
            TroopCount footprint = new TroopCount();
            foreach (Troop troop in Troops)
            {
                switch (troop.Type)
                {
                    case TroopType.AerospaceFighter:
                        footprint.AeroCapacity++;
                        break;
                    case TroopType.Battlemech:
                        footprint.BattlemechCapacity++;
                        break;
                    case TroopType.BattleArmor:
                        footprint.BattleArmorCapacity++;
                        break;
                    case TroopType.Infantry:
                        footprint.TroopCapacity++;
                        break;
                    case TroopType.Vehicle:
                        //TODO: break out heavy vehicles from light ones
                        footprint.VehicleCapacity++;
                        break;
                }
            }
            return footprint;
        }

        public TroopCount GetTotalTroopTransportCapacity()
        {
            Dropships dropships = new Dropships(OwnedDropships);
            dropships.Dropship.AddRange(AuxDropships);
            return dropships.GetTotalStorageCapacity();
        }

        public ushort GetTechnicalSupportNeeded(TroopType type)
        {
            return (ushort)GetVehicles(type).Select(m => (int)m.MaintenanceTimeNeeded).Sum();
        }

        public ushort GetTechnicalSupportAvailable(TroopType type)
        {
            switch(type)
            {
                case TroopType.Battlemech:
                    return (ushort)GetTroops(TroopType.BattlemechTech).SelectMany(t => t.Character).Sum(c => c.GetWeeklyEffectiveHours(TroopType.BattlemechTech));
                case TroopType.AerospaceFighter:
                    return (ushort)GetTroops(TroopType.AerospaceTech).SelectMany(t => t.Character).Sum(c => c.GetWeeklyEffectiveHours(TroopType.AerospaceTech));
                case TroopType.Vehicle:
                    return (ushort)GetTroops(TroopType.VehicleMechanic).SelectMany(t => t.Character).Sum(c => c.GetWeeklyEffectiveHours(TroopType.VehicleMechanic));
                default:
                    return 0;
            }
        }

        public ushort GetMedicalSupportNeeded()
        {
            ushort headCount = (ushort)Personnel.Count();
            return (ushort)((((headCount / 7) + 1) * 3) + (headCount / 5));
        }

        public ushort GetMedicalSupportAvailable()
        {
            return (ushort)GetTroops(TroopType.Medical).SelectMany(t => t.Character).Sum(c => c.GetWeeklyEffectiveHours(TroopType.Medical));
        }

        public ushort GetAdminSupportNeeded()
        {
            ushort techSupportNeeded = (ushort)(GetTechnicalSupportNeeded(TroopType.Battlemech) +
                                                GetTechnicalSupportNeeded(TroopType.AerospaceFighter) +
                                                GetTechnicalSupportNeeded(TroopType.Vehicle) +
                                                GetTechnicalSupportNeeded(TroopType.Dropship) +
                                                GetTechnicalSupportNeeded(TroopType.Jumpship));
            ushort nonAdminPersonnel = (ushort)Troops.Where(t => t.Type != TroopType.Administrative)
                                             .SelectMany(t => t.Character).Distinct().Count();
            return (ushort)((techSupportNeeded + nonAdminPersonnel) / 30);
        }

        public ushort GetAdminSupportAvailable()
        {
            return (ushort)GetTroops(TroopType.Administrative).SelectMany(t => t.Character).Sum(c => c.GetWeeklyEffectiveHours(TroopType.Administrative));
        }

        public ushort GetLengthOfDebt()
        {
            if (CurrentBalance >= 0)
            {
                return 0;
            }
            else
            {
                ushort monthsOfDebt = 1;
                var sortedMonthlyHistory = History.MonthlyHistory.OrderByDescending(h => h.Value).OrderByDescending(h => h.Year);
                int count = sortedMonthlyHistory.Count();
                
                for (int i = 0; i < count; i++)
                {
                    if (sortedMonthlyHistory.ElementAt(i).Balance >= 0)
                    {
                        break;
                    }
                    monthsOfDebt++;
                }
                return monthsOfDebt;
            }
        }

        public int GetContractResultCount(ContractResult resultType)
        {
            return History.ContractHistory.Where(c => c.ContractResult == resultType).Count();
        }

        public double GetTotalSalary(IEra era, SalaryModifiers salaryModifiers)
        {
            return Personnel.Sum(p => p.GetSalary(era, salaryModifiers));
        }

        public double GetTotalMaintenanceCosts(IEra era, MaintenanceCosts maintenanceCosts)
        {
            double totalCost = 0;

            foreach (Troop troop in Troops)
            {
                MaintenanceCost baseCost = maintenanceCosts.MaintenanceCost.Where(mc => mc.TroopType == troop.Type).FirstOrDefault();
                if(baseCost == null)
                {
                    throw new ArgumentOutOfRangeException("Troop type " + troop.Type.ToString() + " is not in the maintenance cost list!");
                }
                totalCost += baseCost.MonthlyCost * era.MaintenanceMultiplier;
            }

            int personnelCount = Personnel.Count();
            MaintenanceCost infCost = maintenanceCosts.MaintenanceCost.Where(mc => mc.TroopType == TroopType.Infantry).FirstOrDefault();
            if (infCost == null)
            {
                throw new ArgumentOutOfRangeException("Infantry is not in the maintenance cost list!");
            }
            totalCost += Math.Ceiling(personnelCount / 7.0) * infCost.MonthlyCost * era.MaintenanceMultiplier;

            return totalCost;
        }

        private Quality CalculateAverageQuality(IEnumerable<ForceCharacter> characters)
        {
            // get average experience rating of all combat units
            float aer = characters.Select(c => (float)(c.PrimarySkills.Sum(s => s.Value)) / c.PrimarySkills.Count).Average();
            if (aer <= 2.5)
            {
                return Quality.Elite;
            }
            else if (aer <= 4)
            {
                return Quality.Veteran;
            }
            else if (aer <= 5.5)
            {
                return Quality.Regular;
            }
            else
            {
                return Quality.Green;
            }
        }
    }
}
